module nazo.file;
/**
 * ファイルから最終更新時刻を求めるAPIを追加します。
 * このファイルをインポートすると自動的にstd.fileをインポートします。
 * Linux上ではstd.fileのreadとwriteにロックを加えます。
 * History:
 *          0.0.0.2 いくつか追加
 *          0.0.0.1 バージョン付け開始
 * Authors: Nazo
 * Version: 0.0.0.1
 * License: Public Domain
 *
 * Bugs: Windows上での動作は未確認
 */
public import std.file;
import nazo.date, std.date, std.string, core.stdc.errno;

interface IFile{
  static string getFileName(string name);
}

///FIXME: alias ProcFile(SaveList) TSaveList;TSaveList!("test") a;がうまくいかない。
template ProcFile(T){
  class ProcFile(string filename):T, IFile{
    public{
      ///ファイル名の取得
      static string getFileName(){
        return filename;
      }
      ///コンストラクタ
      this(){
        super(getFileName());
      }
    }
  }
  class ProcFile(string filenamebefore, string filenameafter):T, IFile{
    public{
      ///ファイル名の取得
      static string getFileName(string name){
        return filenamebefore~name~filenameafter; 
      }
      ///コンストラクタ
      this(string name){
        super(getFileName(name));
      }
    }
  }
}

version(linux){
  import std.c.linux.linux;
  stat_t getStat(string name){
    stat_t statbuf;
    if(std.c.linux.linux.stat(.toStringz(name), &statbuf)){
      return statbuf;
    }
    return statbuf;
  }
}else version(Windows){
  import std.c.windows.windows, std.windows.charset, std.utf;
  WIN32_FIND_DATA getFd(string name){
    WIN32_FIND_DATA filefindbuf;
    FindFirstFileA(toMBSz(name), &filefindbuf);
    return filefindbuf;
  }
  WIN32_FIND_DATAW getFdW(string name){
    WIN32_FIND_DATAW filefindbuf;
    FindFirstFileW(.toUTF16z(name), &filefindbuf);
    return filefindbuf;
  }
}

import std.stdio;
///最終更新時刻を取得します。
d_time getLastWriteTime(string name){
  version(linux){
    return getUTCtimeFromUtime(getStat(name).st_mtime);
  }else version(Windows){
    if(useWfuncs){
      return std.date.FILETIME2d_time(getFdW(name).ftLastWriteTime);
    }else{
      return std.date.FILETIME2d_time(getFd(name).ftLastWriteTime);
    }
  }
}
///作成時刻を取得します。
d_time getCreationTime(string name){
  version(linux){
    return getUTCtimeFromUtime(getStat(name).st_ctime);
  }else version(Windows){
    if(useWfuncs){
      return std.date.FILETIME2d_time(getFdW(name).ftCreationTime);
    }else{
      return std.date.FILETIME2d_time(getFd(name).ftCreationTime);
    }
  }
}
///最終アクセス時刻を取得します。
d_time getLastAccessTime(string name){
  version(linux){
    return getUTCtimeFromUtime(getStat(name).st_atime);
  }else version(Windows){
    if(useWfuncs){
      return std.date.FILETIME2d_time(getFdW(name).ftLastAccessTime);
    }else{
      return std.date.FILETIME2d_time(getFd(name).ftLastAccessTime);
    }
  }
}

ulong getSize(string name){
  version(linux){
    return getStat(name).st_size;
  }else version(Windows){
    if(useWfuncs){
      auto fd=getFdW(name);
      return cast(ulong)fd.nFileSizeHigh<<32 | fd.nFileSizeLow;
    }else{
      auto fd=getFd(name);
      return cast(ulong)fd.nFileSizeHigh<<32 | fd.nFileSizeLow;
    }
  }
}

version(linux):
import nazo.lock, std.c.stdio, std.c.stdlib;

/********************************************
 * Read a file with lock.
 * Returns:
 *	array of bytes read
 */

void[] read(string name)
{
//writefln("\n\n%s",name);
    uint size;
    uint numread;
    int fd;
    stat_t statbuf;
    byte[] buf;
    const(char) *namez;

    namez = toStringz(name);
    //printf("file.read('%s')\n",namez);
    fd = std.c.linux.linux.open(namez, O_RDONLY);
    if (fd == -1)
    {
        //printf("\topen error, errno = %d\n",errno);
        goto err1;
    }

    //printf("\tfile opened\n");
    if (std.c.linux.linux.fstat(fd, &statbuf))
    {
        //printf("\tfstat error, errno = %d\n",errno);
        goto err2;
    }
    size = statbuf.st_size;
    buf = new byte[size];

    nazo.lock.flock( fd, LOCK_SH );
    numread = std.c.linux.linux.read(fd, cast(char*)buf, size);
    nazo.lock.flock( fd, LOCK_UN );
    if (numread != size)
    {
        //printf("\tread error, errno = %d\n",errno);
        goto err2;
    }

    if (std.c.linux.linux.close(fd) == -1)
    {
	//printf("\tclose error, errno = %d\n",errno);
        goto err;
    }

    return buf;

err2:
    std.c.linux.linux.close(fd);
err:
    delete buf;

err1:
    throw new FileException(name, errno);
}

/*********************************************
 * Write a file with lock.
 * Returns:
 *	0	success
 */

void write(string name, const(void)[] buffer)
{
    int fd;
    int numwritten;
    const(char) *namez;

    namez = toStringz(name);
    fd = std.c.linux.linux.open(namez, O_CREAT | O_WRONLY | O_TRUNC/+ | O_DIRECT /+アライメントを揃える必要あり+/+/, 0660);
    if (fd == -1)
        goto err;

    nazo.lock.flock( fd, LOCK_EX );
    numwritten = std.c.linux.linux.write(fd, buffer.ptr, buffer.length);
    nazo.lock.flock( fd, LOCK_UN );
    if (buffer.length != numwritten)
        goto err2;

    if (std.c.linux.linux.close(fd) == -1)
        goto err;

    return;

err2:
    std.c.linux.linux.close(fd);
err:
    throw new FileException(name, errno);
}


/*********************************************
 * Append to a file with lock.
 */

void append(string name, const(void)[] buffer)
{
    int fd;
    int numwritten;
    const(char) *namez;

    namez = toStringz(name);
    fd = std.c.linux.linux.open(namez, O_APPEND | O_WRONLY | O_CREAT, 0660);
    if (fd == -1)
        goto err;
    nazo.lock.flock( fd, LOCK_EX );
    numwritten = std.c.linux.linux.write(fd, buffer.ptr, buffer.length);
    nazo.lock.flock( fd, LOCK_UN );
    if (buffer.length != numwritten)
        goto err2;

    if (std.c.linux.linux.close(fd) == -1)
        goto err;

    return;

err2:
    std.c.linux.linux.close(fd);
err:
    throw new FileException(name, errno);
}
